#include "cppinc/kzlink.h"
#include <stdio.h>
#include <string.h>
#include "cppinc/log.h"

extern void LOG(const char *__format, ...);

void kzlink::InitRingBufRx(uint8_t* rxbuff, uint32_t rxDepth)
{
    m_rxbuff = rxbuff;
    m_rxbuffMirro = new uint8_t(rxDepth);
    m_rxDepth = rxDepth;
    m_head = rxbuff;
    m_tail = rxbuff;
}

void kzlink::InitRingBufTx(uint8_t* txbuff, uint32_t txDepth)
{
    m_txbuff = txbuff;
    m_txDepth = txDepth;
}

const char* CioStaStr(CioSta sta)
{
    switch (sta) {
        case CIO_RDY:     return "CIO_RDY";
        case CIO_PROCING: return "CIO_PROCING";
        case CIO_OVF:     return "CIO_OVF";
        default:
            return "udefined";
    }
}

void kzlink::Dump(void)
{
    LOG("CioSta[%u]\r\n", CioStaStr(m_sta));
    LOG("RxBuffer[0x%08x] Len[%u]\r\n", m_rxbuff, m_rxDepth);
    LOG("  Total-Valid-Err: %u-%u-%u\r\n", m_rxRecvedTotal, m_rxRecvedValid, m_rxRecvedTotal - m_rxRecvedValid);
    LOG("TxBuffer[0x%08x] Len[%u]\r\n", m_txbuff, m_rxDepth);
    LOG("  tx_func[0x%08x]\r\n", m_txFunc);
    LOG("cb:\r\n");
    for (uint32_t type = INVALID; type < FRM_TYPE_MAX; type++) {
        if (m_exe[type] == nullptr) {
            continue;
        }
        LOG("frm[%02x] -> func[0x%08x]\r\n", type, m_exe[type]);
    }
    PrintUsageBar();
}

void kzlink::PrintUsageBar()
{
    uint8_t* head = m_head;
    uint8_t* tail = m_tail;
    uint32_t step = m_rxDepth / 40;
    if (head <= tail) {
        LOG("UsageRate[%u/%u] ", m_tail - m_head, m_rxDepth);
        for (const uint8_t* p = m_rxbuff; p < head; p += step) {
            LOG("_");
        }
        LOG("[");
        for (uint8_t* p = head; p < tail; p += step) {
            LOG("*");
        }
        LOG("]");
        for (uint8_t* p = tail; p < head + m_rxDepth; p += step) {
            LOG("_");
        }
    } else {
        LOG("UsageRate[%u/%u] ", m_rxDepth + m_tail - m_head, m_rxDepth);
        for (const uint8_t* p = m_rxbuff; p < tail; p += step) {
            LOG("*");
        }
        LOG("]");
        for (uint8_t* p = head; p < tail; p += step) {
            LOG("_");
        }
        LOG("[");
        for (uint8_t* p = tail; p < head + m_rxDepth; p += step) {
            LOG("*");
        }
    }
    LOG("\r\n");
}



void kzlink::RegExe(uint32_t pkgType, uint32_t size, RingIoHandle exe)
{
    if (pkgType >= FRM_TYPE_MAX) {
        return;
    }
    m_pkgLen[pkgType] = size;
    m_exe[pkgType] = exe;
    LOG("kzlink: reg: type[%02x](%d bytes), handle 0x%08x\r\n", pkgType, size, exe);
}

ERRCODE kzlink::SendHandle(pkgType type, const void* src, uint32_t len)
{
    if (m_txFunc == nullptr) {
        LOG("FATAL kzlink: tx func unconfiged\r\n");
        return ERR_VIRTFUNC;
    }

    pkg* o = (pkg*)m_txbuff;
    memset(m_txbuff, 0, m_txDepth);
    o->type = type;
    o->dataLen = len;
    memcpy(o->head, m_frmHead, FRMHEAD_LEN_MAX);
    const uint8_t* pRcc = NULL;
    uint32_t rcc = 0;
    for (pRcc = m_txbuff; pRcc != m_txbuff + PKG_DATA_OFFSET; pRcc++) {
        rcc += *pRcc;
    }
    for (pRcc = (const uint8_t*)src; pRcc != (const uint8_t*)(src) + len; pRcc++) {
        rcc += *pRcc;
    }
    memcpy(m_txbuff + PKG_DATA_OFFSET, src, len);
    memcpy(m_txbuff + PKG_DATA_OFFSET + len, (const uint8_t*)&rcc, sizeof(rcc));

    return m_txFunc(m_txbuff, PKG_DATA_OFFSET + len + sizeof(rcc));
}

ERRCODE kzlink::ValidPkg(const uint8_t* data, size_t len) const
{
    if (data == NULL || len < sizeof(pkg)) {
        return FRM_LENERR;
    }
    const pkg* o = (const pkg*)data;
    /* check head */
    if (memcmp(o->head, m_frmHead, FRMHEAD_LEN_MAX) != 0) {
        return FRM_HEADERR;
    }
    /* check length */
    uint32_t dataLen = o->dataLen;
    uint32_t pkgType = o->type;
    if (pkgType >= FRM_TYPE_MAX || dataLen != m_pkgLen[pkgType]) {
        return FRM_LENERR;
    }
    /* check rcc */
    const uint8_t* dataAddr = data + PKG_DATA_OFFSET;
    uint32_t rccRcv = *((uint32_t*)(dataAddr + o->dataLen));
    uint32_t rcc = 0;
    for (const uint8_t* pData = data; pData != dataAddr + o->dataLen; pData++) {
        rcc += *pData;
    }
    if (rcc != rccRcv) {
        return FRM_RCCERR;
    }

    return  FRM_OK;
}

pkgType kzlink::GetType(const uint8_t* data, size_t len) const
{
    if (data == NULL || len < sizeof(pkg)) {
        return INVALID;
    }
    const pkg* o = (const pkg*)data;
    return o->type;
}

const uint8_t*  kzlink::GetPayload(const uint8_t* data, size_t len) const
{
    if (data == NULL || len < sizeof(pkg)) {
        return NULL;
    }
    const pkg* o = (const pkg*)data;
    return o->data;
}

uint32_t kzlink::GetPayloadLen(const uint8_t* data, size_t len) const
{
    if (data == NULL || len < sizeof(pkg)) {
        return 0;
    }
    const pkg* o = (const pkg*)data;
    return o->dataLen;
}

ERRCODE kzlink::EchoAck(ERRCODE errCode, uint32_t recvCmdID, const uint8_t* data, size_t len)
{
    FrmDevAck frm;

    memset((uint8_t*)&frm, 0xFF, sizeof(FrmDevAck));
    frm.errCode = errCode;
    frm.recvCmdID = recvCmdID;
    if (data != NULL && len <= ACK_DATA_LEN) {
        memcpy(frm.data, data, len);
    }

    return SendHandle(DEV_ACK, (uint8_t*)&frm, sizeof(frm));
}

void kzlink::FlushRx()
{
    m_rxRecvedTotal += m_inComingBytesCnt;
    m_inComingBytesCnt = 0;
    memset(m_rxbuff, 0, m_rxDepth);
}

void kzlink::ConsumeRx()
{
    if (m_inComingBytesCnt == 0) {
        return;
    }
    ERRCODE ret = ValidPkg(m_rxbuff, m_inComingBytesCnt);
    pkgType type = GetType(m_rxbuff, m_inComingBytesCnt);
    if (ret != FRM_OK) {
        FlushRx();
        EchoAck(ret, type, NULL, 0);
        return;
    }
    if (m_exe[type] == nullptr) {
        FlushRx();
        EchoAck(ERR_CMDNREG, type, NULL, 0);
        return;
    }
    m_rxRecvedValid += m_inComingBytesCnt;
    const uint8_t* payload = GetPayload(m_rxbuff, m_inComingBytesCnt);
    uint32_t payloadLen = GetPayloadLen(m_rxbuff, m_inComingBytesCnt);
    ret = m_exe[type](payload, payloadLen);
    FlushRx();
}